package relu.R.landscape;

import java.util.Iterator;

import relu.R.birds.*;
import relu.R.weeds.IFSSorter;
import relu.R.weeds.WeedSpecies;
import jfm.mou.FarmerMOU;
import java.util.*;
import jfm.utils.*;
import jfm.xml.XMLSyntaxException;
import jfm.model.Types.CropType;
import jfm.model.Types;

/** Type of landscape in which bird home-ranges are embedded within individual farms 
 * and farms are independent across the landscape */
public class NonSpatialLandscape extends Landscape {
	private List<Double> representedArea=new ArrayList<Double>();// Area represented by this farm type Ha
	private List<Double> sideLengths=new ArrayList<Double>();// Side lengths of farms in Km
	private Map<Integer,ArrayList<Bird>> birdsByFarm=new HashMap<Integer,ArrayList<Bird>>();
	private ArrayList<Double> birdPopulationHistory=new ArrayList<Double>();

	public int numBirds(Integer farmI){
		if (birdsByFarm.containsKey(farmI)){
			return birdsByFarm.get(farmI).size();
		} else {
			throw new Error("No farm with index "+farmI);
		}
	}
	
	public void setDistanceFromSugarbeetFactory(double distance,double costPerKmPerHa){
		for(LSFarm fm:farms){
			fm.setDistanceFromSugarbeetFactory(distance, costPerKmPerHa);
		}
	}
	
	public int isNullPoint(){return isNullPoint;};
	public int numBirds(){
		int b=0;
		for(ArrayList<Bird> fbds:birdsByFarm.values()){
			b+=fbds.size();
		}
		return b;
	}
	
	public double getAverageProfit(){
		double prof=0;
		for(LSFarm fm:farms){
			prof+=fm.getProfit();
		}
		return prof;
	}
	
	public double getWinterStubbleArea(){
		double ws=0;
		for(LSFarm fm:farms){
			ws+=fm.getWinterStubbleArea();
		}
		return ws;
	}
	
	/** Returns the total ie density*area .. divide by total area to get av density */
	public double getWeedPopulation(String speciesName){
		WeedSpecies sp=IFSSorter.stringToSpecies.get(speciesName);
		double pop=0;
		for(LSFarm fm:farms){
			pop+=fm.getWeedPopulation(sp)*(fm.area);
		}
		return pop;
	}
	
	public double getProportionInStubble(){
		double ps=0;
		double n=0;
		for(LSFarm fm:farms){
			ps+=fm.getProportionOfFieldsInStubble()*fm.fields.size();
			n+=fm.fields.size();
		}
		return ps/(double)n;
	}
	
	public double getCropArea(String cropName){
		double area=0;
		try {
			CropType cropType = Types.xmlToCropType(cropName);
			for(LSFarm fm:farms){
				area+=fm.cropArea(cropType);
			}
		} catch (XMLSyntaxException ex){
			throw new Error(ex.getMessage());
		}
		return area;
	}
	
	// In this routine the actual farm areas are used as represented areas
	public void initWithRepresentativeFarms(ArrayList<LSFarm> farms_){
//		ArrayList<LSFarm> farms_ = dist.createRepresentativeFarms(nFarms,(Landscape)this);
		farms.addAll(farms_);
		for(LSFarm fm:farms){
		//	System.out.println("Initialising farm with area "+fm.area);
			representedArea.add(fm.area);
			sideLengths.add(Math.sqrt(fm.area*0.01));
		}
	}
	
	@Override
	public void addFarm(double inArea, String tmplXML, String mouXML) {
		FarmerMOU fmou = FarmerMOU.fromXML(mouXML);
	//	System.out.println("Adding farm "+tmplXML+" mouXML "+inArea);

		LSFarm farm=LSFarm.fromXML(tmplXML,(Landscape)this,null,inArea);

		farm.applyMOUToFarm(fmou);
		farms.add(farm);
		representedArea.add(inArea);// In hectares
		sideLengths.add(Math.sqrt(inArea*0.01));// In kilometers
//		throw new Error("addFarm shouldn't be called under geographiclocationdist");
	}

	
	private double[] areaWeights(){
		double[] weights=new double[farms.size()];
		double totalArea=JFMMath.sum(representedArea);
		int di=0;
		for(Double d:representedArea){
			weights[di]=d/totalArea;
			di++;
		}
		return weights;
	}
	
	public double getHedgerowDensity(){
		double hd=0;
		for(LSFarm fm :farms){
			hd+=fm.getHedgeDensity();
		}
		return hd;
	}
	
	private double[] birdWeights(){
		double[] weights=new double[farms.size()];
		int di=0;
		for(Double d:representedArea){
			weights[di]=d*BreedingHabitats.getHabitatProbability(farms.get(di));
		//	System.out.println("Farm area "+d+" "+weights[di]);
			di++;
		}
		if ( JFMMath.sum(weights)== 0){
			// Just put in a random point somewhere 
			System.out.println("Warning No suitable habitats");
			weights[0]=1;
		}
		JFMMath.normalize(weights);
		return weights;
	}

	// In this case we assign a total number of fields in proportion to the area of each farm 
	public void createAndDistributeFields(int nFields) {
	//	System.out.println("Creating and distributing fields");
		fields.clear();
		double[] weights=areaWeights();
		double totalArea=JFMMath.sum(representedArea);
		for(int i=0;i<nFields;i++){
			int frmIndex=JFMMath.chooseWeightedRandom(weights, Landscape.rGen.nextDouble());
			double sideLength=sideLengths.get(frmIndex);
			// Spatial factors at the scale of a single farm are taken into account
			double x=rGen.nextDouble()*sideLength;
			double y=rGen.nextDouble()*sideLength;
			Field theField=new Field(x,y,sideLength,totalArea/(double)nFields);
			fields.add(theField);
			LSFarm farm=farms.get(frmIndex);
			theField.assignToFarm(farm);
		}
	}
	
	public void setWinterStubbleSubsidy(double value){
		for(LSFarm fm:farms){
			fm.setWinterStubbleSubsidy(value);
		}
	}

	public void applySocioEconomicDistribution(EnviroVsProfitDistribution dist){
		for(LSFarm fm:farms){
			dist.sampleFarmer(fm);
		}
	}
	
	public void applySocioEconomicDistribution(GeographicLocationDistribution dist){
		for(LSFarm fm:farms){
			dist.sampleFarmer(fm);
		}
	}
	
	public void applySocioEconomicDistribution(DualGeographicLocationDistribution dist){
		for(LSFarm fm:farms){
			dist.sampleFarmer(fm);
		}
	}
	
	@Override
	public void assignFieldsToBirds() {
		for(int i=0;i<farms.size();i++){
			LSFarm farm=farms.get(i);
			for(Field fld:farm.fields){
				fld.assignToAllBirdsInRange(birdsByFarm.get(i));
			}
		}
	}

	@Override
	public void assignFieldsToFarmers() {} // Not needed

	public double getTotalBirdFood(){
		double bf=0;
		System.out.println(birds.size());
		for(Bird bd:birds){
			bf+=bd.availableShare()/(bd.species.dailyRation*Landscape.nWinterDays);
		}
		return bf;
	}

	
	protected double doBirdWinterSurvival(){
		int b=0;
		double surv=0;
		deadBirds.clear();
//		System.out.println(birds.size());		
		for(Bird bd:birds){
//			System.out.println(b);
//			b++;
			if ( !bd.enoughFood() || (rGen.nextDouble() > bd.species.rsurv)){
				deadBirds.add(bd);
			}
		}
		if ( birds.size() > 0 ){
			surv=1-deadBirds.size()/(double)birds.size();
		} else {
			surv=0;
		}
		birds.removeAll(deadBirds);
		for(ArrayList<Bird> fbirdList:birdsByFarm.values()){
			fbirdList.removeAll(deadBirds);
		}
		// Don't allow populations to go extinct
		for(BirdSpecies sp:birdSpecies){
			if ( birds.size()==0 ){
				this.createSummerBirdPopulations(1, sp);
			}
		}		
		birdPopulationHistory.add((double)birds.size());
	//	System.out.println(birds.size());
		return surv;
	}
	
	public double stepSummer(int nBirds){
		double floaters=0;
	//	System.out.println("Stepping year");
		// Update weed populations on all the fields
	//	System.out.println("Rotating Crops and propagating weeds ");
		for(Field fld:fields){
			try {
				fld.rotateCropsAndPropagateWeeds();
			} catch (NullPointException ex){
				isNullPoint=1;
				return 0;// Don't do anything
			}
		}
		for(BirdSpecies bsp:birdSpecies){
			if (nBirds>=0){
	//			System.out.println("Creating summer bird pops");	
				floaters=createSummerBirdPopulations(nBirds,bsp);
			} else {
				// do a normal population dynamics run in which we keep the previous timestep's birds
			}
		}
		assignFieldsToBirds();
		for(BirdSpecies bsp:birdSpecies){
			floaters=doBirdBreeding(bsp);
		}
		return floaters;
	//	System.out.println("assigning fields to birds");
	}
	private double doBirdBreeding(BirdSpecies sp){
		double floaters=0;
		for(int fm=0;fm<farms.size();fm++){
			ArrayList<Bird> fbirds=birdsByFarm.get(fm);
			ArrayList<Bird> young=new ArrayList<Bird>();
			Collections.shuffle(fbirds);
			int b=0;
			int maxBreeders=(int)(BreedingHabitats.getMaxBreeders(farms.get(fm),sp));
			if ( maxBreeders < 5 ){ maxBreeders=5;}
		//	System.out.println(farms.get(fm).getHedgeDensity());
	//		System.out.println("Allowing "+fbirds.size()+" "+maxBreeders+" to breed");
			
//			double floaterProp=fbirds.size()/maxBreeders;
//			if ( floaterProp > 2.0 ){
//				System.out.println("Warning .. significant number of floaters "+floaterProp+" "+
//						fbirds.size()+" "+maxBreeders);
//			}
			
			for(Bird bd:fbirds){	
				if (b<maxBreeders){
					young.addAll(bd.breed(farms.get(fm),sideLengths.get(fm)));
				} else {
					floaters+=1;
				}
				b++;
			}
	//		System.out.println("Created "+young.size()+" new birds ");
			fbirds.addAll(young);
			birds.addAll(young);
			farms.get(fm).assignFieldsToBirds(young);
		}
		if ( birds.size()>0){
			floaters=floaters/birds.size();
		}
		return floaters;
	}
	
	
	
	public double createSummerBirdPopulations(int nBirds, BirdSpecies sp) {
	//	System.out.println("Creating Summer Bird pops");
		birds.clear();
		double floaters=0;
		for(int i=0;i<farms.size();i++){
			if ( birdsByFarm.containsKey(i)){
				birdsByFarm.get(i).clear();
			} else {
				birdsByFarm.put(i, new ArrayList<Bird>());
			}
		}
		
		double[] weights=birdWeights();
		JFMMath.normalize(weights);
		for(int i=0;i<nBirds;i++){
			// Choose the farm that the birds are most likely to breed on
			int frmIndex = JFMMath.chooseWeightedRandom(weights, Landscape.rGen.nextDouble());
//			System.out.println(frmIndex);
//			for(int frmIndex=0;frmIndex<farms.size();frmIndex++){
				double x=rGen.nextDouble()*sideLengths.get(frmIndex);
				double y=rGen.nextDouble()*sideLengths.get(frmIndex);
//				int maxBirds=(int)(weights[frmIndex]*600);
//				int maxBirds=1000000000;
				
				// Find the maximum number of birds that can breed on this farm
				int maxBirds=(int)BreedingHabitats.getMaxBreeders(farms.get(frmIndex), sp);
				if ( maxBirds < 5 ){
					maxBirds=5;
				}
//				System.out.println(maxBirds);
				if ( birdsByFarm.get(frmIndex).size() < maxBirds){
					Bird theBird=new Bird(sp,x,y);
					birds.add(theBird);
					birdsByFarm.get(frmIndex).add(theBird);
				} else {
					floaters+=1.0;
				}
//			}
		}
//		System.out.println(birdsByFarm.get(0).size()+" "+birdsByFarm.get(1));
//		throw new Error();
		if ( birds.size()> 0){
			return floaters/(double)birds.size();
		} else {
			return 0.0;
		}
	}
	

}
